import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class lsnode
{
    int localPort;
    ArrayList<Node> neighbor = new ArrayList<Node>();
    boolean isLast;
    private Listen listener;
    HashMap<Integer, ArrayList<Node>> graph = new HashMap<Integer, ArrayList<Node>>();
    HashMap<Integer, Integer> routingTable = new HashMap<Integer, Integer>();
    SendAndReceive port;
    ArrayList<DijkstraNode> information = new ArrayList<DijkstraNode>();
    int count = 0;
    boolean workerStarted;

    class Worker implements Runnable
    {

        @Override
        public void run()
        {
            try
            {
                Thread.sleep(1000);// wait 1 second to broadcast
            }
            catch (InterruptedException e2)
            {
                // TODO Auto-generated catch block
                e2.printStackTrace();
            }
            if (count == neighbor.size())
            {
                // flooding
                System.out.println("");
                System.out.println("Message flooding started.");
                broadcastMe();
                try
                {
                    Thread.sleep(5 * 1000);
                }
                catch (InterruptedException e1)
                {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }

                System.out.println("Message flooding finished.");
                System.out.println("");

                System.out.println("Node (Port " + localPort + ") received the following link-state information:");
                System.out.println("");

                System.out.println("source Node (Port " + localPort + "):");

                for (Node i : graph.get(localPort))
                {
                    if (i.portNumber != localPort)
                        System.out.println("Neighbors Node (Port " + i.portNumber + ") (Cost " + i.distance + ").");
                }

                System.out.println("");
                System.out.println("Node (Port " + localPort + ") starts routing table construction.");
                System.out.println("");
                runDijkstra();

                try
                {
                    Thread.sleep(5 * 1000);
                }
                catch (InterruptedException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                if (isLast)
                {
                    int furthestIndex = 0;
                    for (int i = 1; i < information.size(); i++)
                    {
                        if (information.get(i).distance > information.get(furthestIndex).distance)
                        {
                            furthestIndex = i;
                        }
                    }
                    System.out.println("");
                    System.out.println("Send a data packet");

                    int furthestPort = information.get(furthestIndex).port;
                    SendDataPacket me = new SendDataPacket(furthestPort, 321);
                    Packet packet = new Packet(Packet.PacketType.SendData, me);
                    port.send(packet, "127.0.0.1", routingTable.get(furthestPort));

                }
                
                try
                {
                    Thread.sleep(2 * 1000);// wait 2 sec
                }
                catch (InterruptedException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                System.exit(-1);

            }
            else
            {
                for (int i = 0; i < neighbor.size(); i++)
                {
                    if (neighbor.get(i).isVerified)
                    {
                        System.out.println("Node (Port " + neighbor.get(i).portNumber + ") can not reach Node (Port "
                                + localPort + ")");
                        System.out.println("Link failture occurred. Program terminated.");
                        System.exit(-1);
                    }
                }
            }
        }

    }

    /**
     * @param args
     */
    public static boolean isNumeric(String str)
    {
        Pattern pattern = Pattern.compile("[0-9]*");
        Matcher isNum = pattern.matcher(str);
        if (!isNum.matches())
        {
            return false;
        }
        return true;
    }

    public static boolean isPortNumber(String str)
    {
        if (isNumeric(str))
        {
            int port = Integer.parseInt(str);
            if (port > 1024)
            {
                return true;// TODO: initiate the server
            }
        }
        return false;
    }

    public static void main(String[] args)
    {
        lsnode test = null;
        try
        {
            test = new lsnode(args);
        }
        catch (Exception e)
        {
            System.out.println("Invalid command line arguments.");
            System.exit(1);
        }

        while (true)
            ;
        /*
         * ArrayList<Node> A = new ArrayList<Node>(); A.add(new Node(2002, 3));
         * A.add(new Node(2010, 2)); A.add(new Node(2011, 7));
         * 
         * ArrayList<Node> B = new ArrayList<Node>(); B.add(new Node(2003, 2));
         * B.add(new Node(2008, 1)); B.add(new Node(2009, 9)); B.add(new
         * Node(2010, 12)); B.add(new Node(2001, 3));
         * 
         * ArrayList<Node> C = new ArrayList<Node>(); C.add(new Node(2002, 2));
         * C.add(new Node(2008, 2)); C.add(new Node(2004, 2));
         * 
         * ArrayList<Node> D = new ArrayList<Node>(); D.add(new Node(2003, 2));
         * D.add(new Node(2008, 2)); D.add(new Node(2007, 2)); D.add(new
         * Node(2005, 3));
         * 
         * ArrayList<Node> E = new ArrayList<Node>(); E.add(new Node(2004, 3));
         * E.add(new Node(2007, 1)); E.add(new Node(2006, 1));
         * 
         * ArrayList<Node> F = new ArrayList<Node>(); F.add(new Node(2011, 2));
         * F.add(new Node(2007, 3)); F.add(new Node(2005, 1));
         * 
         * ArrayList<Node> G = new ArrayList<Node>(); G.add(new Node(2005, 1));
         * G.add(new Node(2004, 2)); G.add(new Node(2008, 2)); G.add(new
         * Node(2009, 6)); G.add(new Node(2006, 3));
         * 
         * ArrayList<Node> H = new ArrayList<Node>(); H.add(new Node(2003, 2));
         * H.add(new Node(2004, 2)); H.add(new Node(2007, 2)); H.add(new
         * Node(2009, 8)); H.add(new Node(2002, 1));
         * 
         * ArrayList<Node> I = new ArrayList<Node>(); I.add(new Node(2002, 9));
         * I.add(new Node(2010, 1)); I.add(new Node(2008, 8)); I.add(new
         * Node(2007, 6)); I.add(new Node(2011, 1));
         * 
         * ArrayList<Node> J = new ArrayList<Node>(); J.add(new Node(2002, 12));
         * J.add(new Node(2001, 2)); J.add(new Node(2011, 4)); J.add(new
         * Node(2009, 1));
         * 
         * ArrayList<Node> K = new ArrayList<Node>(); K.add(new Node(2006, 2));
         * K.add(new Node(2001, 7)); K.add(new Node(2010, 4)); K.add(new
         * Node(2009, 1));
         * 
         * test.graph.put(2001, A); test.graph.put(2002, B);
         * test.graph.put(2003, C); test.graph.put(2004, D);
         * test.graph.put(2005, E); test.graph.put(2006, F);
         * test.graph.put(2007, G); test.graph.put(2008, H);
         * test.graph.put(2009, I); test.graph.put(2010, J);
         * test.graph.put(2011, K); test.localPort = 2001;
         */

    }

    public lsnode(String[] args)
    {
        if (args.length > 32)
        {
            System.out.println("Exceed maximum number of nodes.");
            System.exit(1);
        }
        if (isPortNumber(args[0]))
        {
            localPort = Integer.parseInt(args[0]);
        }

        isLast = args[args.length - 1].equals("last");

        for (int i = 1; i < args.length - 1; i += 2)
        {
            Node neighborNode = new Node(Integer.parseInt(args[i]), Integer.parseInt(args[i + 1]));
            neighbor.add(neighborNode);
        }

        port = new SendAndReceive(localPort);
        listener = new Listen();
        Thread listen = new Thread(listener);
        listen.start();
        if (isLast)
        {
            verify();
            workerStarted = true;
            new Thread(new Worker()).start();
        }
    }

    class Listen implements Runnable
    {
        public void run()
        {
            while (true)
            {
                
                // process packet while listening
                
                DatagramPacket receive = port.receive(); // receive
                Packet packet = (Packet) Serializer.byteArrayToObject(receive.getData());
                System.out.println("Message received at Node (Port " + localPort + ") from Node (Port "
                        + receive.getPort() + ")");

                if (packet.type == Packet.PacketType.Broadcasting)
                {
                    Broadcast msg = (Broadcast) packet.data;

                    if (!graph.containsKey(msg.localPort))
                    {
                        graph.put(msg.localPort, msg.neighbor);
                        sendToNeighbor(packet);
                        // printGraph();
                        broadcastMe();
                    }

                }

                if (packet.type == Packet.PacketType.SendData)
                {
                    SendDataPacket data = (SendDataPacket) packet.data;
                    int destination = data.destination;
                    if (destination != localPort)
                    {
                        sendPacket(destination, packet);
                    }
                    else
                        System.out.println("Data packet received " + data.data);
                }

                if (packet.type == Packet.PacketType.Verify)
                {
                    if (!workerStarted)
                    {
                        workerStarted = true;
                        new Thread(new Worker()).start();
                    }
                    for (int i = 0; i < neighbor.size(); i++)
                    {
                        if (neighbor.get(i).portNumber == receive.getPort())
                        {
                            if (neighbor.get(i).requestSend)
                            {
                                neighbor.get(i).isVerified = true;
                                count++;

                            }
                            if (!neighbor.get(i).requestSend)
                            {
                                if (neighbor.get(i).distance == (Integer) packet.data)
                                {
                                    neighbor.get(i).isVerified = true;
                                    count++;

                                    if (count == neighbor.size())
                                    {
                                        // System.out.println("all neighbor nodes verified.");

                                    }
                                    Packet packetVerified = new Packet(Packet.PacketType.Verify, null);
                                    port.send(packetVerified, "127.0.0.1", receive.getPort());

                                    verify();
                                }
                                else
                                {
                                    System.out.println("Link cost from Node (Port " + receive.getPort()
                                            + ") to Node (Port " + localPort + ") is " + packet.data + ".");
                                    System.out.println("Link cost from Node (Port " + localPort + ") to Node (Port "
                                            + receive.getPort() + ") is " + neighbor.get(i).distance + ".");
                                    terminate();

                                }
                            }
                        }
                    }
                }

                if (packet.type == Packet.PacketType.Terminate)
                {
                    System.out.println("Link cost does not match. Program terminated.");

                    System.exit(-1);
                }

            }

        }
    }

    public void sendToNeighbor(Packet packet)
    {
        for (int i = 0; i < neighbor.size(); i++)
        {
            port.send(packet, "127.0.0.1", neighbor.get(i).portNumber);
        }

    }

    public void broadcastMe()
    {
        Broadcast me = new Broadcast(localPort, neighbor);
        Packet packet = new Packet(Packet.PacketType.Broadcasting, me);
        sendToNeighbor(packet);
    }

    public void printGraph()
    {
        System.out.println("Starting to print graph");
        for (int i : graph.keySet())
        {
            System.out.println("I am " + i);
            for (Node j : graph.get(i))
                System.out.println("\t" + j.portNumber + "\t " + j.distance);
        }
    }

    public void runDijkstra()
    {

        for (int key : graph.keySet())
        {
            DijkstraNode one;
            if (key == localPort)
            {

                one = new DijkstraNode(key, 0, -1);

            }
            else
            {
                one = new DijkstraNode(key, Integer.MAX_VALUE, -1);

            }
            information.add(one);

        }

        for (int i = 0; i < information.size(); i++)
        {
            int index = extractMin();
            DijkstraNode pointMin = information.get(index);
            pointMin.isAdded = true;

            System.out.print("Step " + i + " ExtractMin PortNumber " + information.get(index).port + " ");

            for (int j = 0; j < information.size(); j++)
            {
                if (information.get(j).distance == Integer.MAX_VALUE)
                {
                    System.out.print(information.get(j).port + ",- ");
                }
                else
                {
                    System.out.print(information.get(j).port + "," + information.get(j).distance + " ");
                }
            }

            System.out.println("");
            // for each neighbour of information[index]
            for (Node key : graph.get(pointMin.port))
            {
                int neighborindex = -1;
                for (int j = 0; j < information.size(); j++)
                {
                    if (information.get(j).port == key.portNumber)
                    {
                        neighborindex = j;
                        break;
                    }
                }
                if (information.get(neighborindex).distance > pointMin.distance + key.distance)
                {
                    information.get(neighborindex).distance = pointMin.distance + key.distance;
                    information.get(neighborindex).parent = pointMin.port;
                }
            }
            // do relax
        }

        buildRoutingTable();
        // printRoutingTable();

    }

    public int extractMin()
    {
        int min = Integer.MAX_VALUE;
        int index = -1;
        for (int i = 0; i < information.size(); i++)
        {
            if (!information.get(i).isAdded)
            {
                if (information.get(i).distance < min)
                {
                    min = information.get(i).distance;
                    index = i;
                }
            }
        }
        return index;
    }

    public void buildRoutingTable()
    {
        for (int i = 0; i < information.size(); i++)
        {
            if (information.get(i).port != localPort)
            {
                routingTable.put(information.get(i).port, findNext(i));

            }
        }

    }

    private int findNext(int index)
    {
        if (information.get(index).parent == localPort)
            return information.get(index).port;
        else
        {

            for (int j = 0; j < information.size(); j++)
            {
                if (information.get(j).port == information.get(index).parent)
                {
                    return findNext(j);

                }

            }
        }
        return -1;
    }

    public void printRoutingTable()
    {
        for (int key : routingTable.keySet())// For every port in Key set
        {
            System.out.println("Destination " + key + " Next " + routingTable.get(key));
        }

    }

    public void sendPacket(int destination, Packet packet)
    {

        port.send(packet, "127.0.0.1", routingTable.get(destination));

    }

    public void verify()
    {
        for (int i = 0; i < neighbor.size(); i++)
        {
            if (!neighbor.get(i).isVerified)
            {
                Packet packet = new Packet(Packet.PacketType.Verify, neighbor.get(i).distance);
                port.send(packet, "127.0.0.1", neighbor.get(i).portNumber);
                neighbor.get(i).requestSend = true;

            }
        }

    }

    public void terminate()
    {
        for (int i = 0; i < neighbor.size(); i++)
        {
            Packet packet = new Packet(Packet.PacketType.Terminate, null);
            port.send(packet, "127.0.0.1", neighbor.get(i).portNumber);

        }
        System.out.println("Link cost does not match. Program terminated.");
        System.exit(-1);
    }
}
